#ifndef __CONCURRENT_THREAD_HPP
#define __CONCURRENT_THREAD_HPP
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include "common/uncopyable.hpp"

namespace concurrent
{
  class Thread : public common::Uncopyable
  {
  public:
    /**
     * 登録可能な関数の型．
     * functor にしろという話もあるが，それはまた後で．
     */
    typedef void* (*func_type)(void*);

  private:
    const func_type func;
    void* arg_ptr;
    const bool auto_delete;

    // pthread_create
    pthread_t id;

  protected:
    void* get_arg_ptr() const {
      return this->arg_ptr;
    }

    void* reset_arg_ptr(void* p) {
      void* ret = get_arg_ptr();
      this->arg_ptr = p;
      return ret;
    }

    void* release_arg_ptr() {
      return reset_arg_ptr(NULL);
    }

    bool is_auto_delete() const {
      return this->auto_delete;
    }

  private:
    static void cleanup(void* arg) {
      Thread* self = (Thread*)arg;
      if (self->is_auto_delete()) {
        pthread_t tmp = self->id;
        delete self;
        printf("[Debug] delete self on %u\n", (unsigned int)tmp);
      }
    }

    static void* run(void* arg)
    {
      Thread* self = (Thread*)arg;
      void* ret = NULL;
      pthread_cleanup_push(Thread::cleanup, self);

      ret = self->func(self->arg_ptr);
      printf("[Debug] finish self->func on %u\n", (unsigned int)self->id);

      pthread_cleanup_pop(1);
      return ret;
    }

  public:
    static bool equals(pthread_t id1, pthread_t id2) {
      return pthread_equal(id1, id2) != 0;
    }

    static unsigned int current_id_uint() {
      return (unsigned int)Thread::current_id();
    }

    static pthread_t current_id() {
      return pthread_self();
    }

  public:
    Thread(func_type f, void* arg, bool autodel=true)
      :func(f), arg_ptr(arg), auto_delete(autodel)
    {}

    virtual ~Thread() {}

    bool is_self(pthread_t id) {
      return Thread::equals(this->id, id);
    }

    const pthread_t get_id() const {
      return this->id;
    }

    bool start() {
      if (pthread_create(&(this->id), NULL, Thread::run, this) != 0) {
        perror("pthread_create");
        return false;
      }
      return true;
    }

    void interrupt() {
      if (pthread_cancel(this->id) != 0) {
        // ESRCH 対応するスレッドがない
        // TODO
        perror("pthread_cancel");
      }
    }

    void check_cancel() {
      pthread_testcancel();
    }

    void abort();

    void join() {
      const int ret = pthread_join(this->id, NULL);
      switch (ret) {
        case ESRCH:
          // th で指定された ID に対応するスレッドが見つからなかった
          break;
        case EINVAL:
          // th で指定されたスレッドはすでにデタッチされている
          // すでに別のスレッドがスレッド th の終了を待っている
          break;
        case EDEADLK:
          // 引数 th が呼び出しスレッドを指している
          break;
      }
    }
  };

}// namespace concurrent

#endif
